#!/usr/local/lua52/bin/lua
--
-- Very simple HTTP server.
--
-- httpd.srv [bind-address [bind-port]]
--
local cqueues = require"cqueues"
local socket = require"cqueues.socket"
local errno = require"cqueues.errno"
local bind, port = ...
local verbose = false --true


local function fmt(...)
	return string.format(...)
end

local info

if verbose then
	info = function(...)
		io.stderr:write(fmt(...), "\n")
	end
else
	info = function()
		return true
	end
end

local function warn(...)
	io.stderr:write(fmt(...), "\n")
end


local mime = {
	html = "text/html",
	xml  = "application/xml",
	css  = "text/css",
	js   = "text/javascript",
	txt  = "text/plain",
	gif  = "image/gif",
	png  = "image/png",
	jpg  = "image/jpeg",
}


local srv = socket.listen(bind or "127.0.0.1", tonumber(port or 8000))

local loop = cqueues.new()

loop:wrap(function()
	local count = 0

	for con in srv:clients() do
		count = count + 1

		local id = count

		loop:wrap(function()
			local _, ip, port = con:peername()

			info("%s:%d: connected", ip, port)

			local ok, why = pcall(function()
				con:setmode("tl", "tf")

				local get, why = con:read"*l"

				if not get then
					warn("%s:%d: no request (%s)", ip, port, errno.strerror(why))

					return
				end

				info("%s:%d: %s", ip, port, get)

				local hdr = {}

				for h in con:lines"*h" do
					local f, b = string.match(h, "^([^:%s]+)%s*:%s*(.*)$")
					hdr[f] = b
				end

				con:read"*l" -- discard header/body break

				local path = string.match(get, "^%w+%s+/?([^%s]+)") or "/dev/null"

				if not path then
					warn("%s:%d: no path specified", ip, port)

					return
				end

				local file = io.open(path, "r")

				if file then
					con:write"HTTP/1.0 200 OK\n"
					con:write"Connection: close\n"

					local ext = string.match(path, "(%w+)$") or ""
					local ctype = mime[string.lower(ext)] or "application/octet-stream"

					con:write("Content-Type: ", ctype, "\n\n")

					if not string.match(ctype, "^text/") then
						con:setmode(nil, "bf")
					end

					for blk in file:lines(1024) do
						con:write(blk)
					end

					file:close()
				else
					con:write"HTTP/1.0 404 Not Found\n"
					con:write"Connection: close\n\n"
				end

				local ok, why = con:flush()

				if not ok then
					warn("%s:%d: cannot flush (%s)", ip, port, errno.strerror(why))
				end

				con:shutdown"w"

				local junk = con:read(1) -- wait for EOF

				if junk then
					warn("%s:%d: spurious data", ip, port)
				end

				con:shutdown"r"
			end)

			if ok then
				info("%s:%d: disconnected", ip, port)
			else
				warn("%s:%d: %s", ip, port, why)
			end

			con:close()
		end)
	end
end)

while not loop:empty() do
	local ok, err = loop:step()
	if not ok then error("loop.step: " .. err) end
end
